/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.rafkind.resrev.web.page.component;

import com.rafkind.resrev.dao.ReservationDao;
import com.rafkind.resrev.dao.ResourceDao;
import com.rafkind.resrev.dao.UserDao;
import com.rafkind.resrev.data.Reservation;
import com.rafkind.resrev.data.Resource;
import com.rafkind.resrev.data.Status;
import com.rafkind.resrev.service.MailService;
import com.rafkind.resrev.web.Session;
import com.rafkind.resrev.web.page.CalendarPage;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.wicket.Page;
import org.apache.wicket.extensions.yui.calendar.DateField;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.validation.AbstractFormValidator;
import org.apache.wicket.markup.html.form.validation.FormComponentFeedbackBorder;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.validator.AbstractValidator;

/**
 *
 * @author Fictional Hero
 */
public class MakeReservationPanel extends Panel {

  @SpringBean 
  private UserDao userDao;
  
  @SpringBean
  private ResourceDao resourceDao;
  
  @SpringBean
  private ReservationDao reservationDao;
  
  @SpringBean
  private MailService mailService;
  
  private QuantizedTime[] times;
  
  private Model<QuantizedTime> fromTimeModel;
  private Model<QuantizedTime> toTimeModel;    
  
  public MakeReservationPanel(String id, IModel<Reservation> model, 
      final Page previousPage) {
    super(id, model);
    
    makeTimes();
    
    Form reservationForm = new Form("reservationForm"){

      @Override
      protected void onSubmit() {
        Reservation res = (Reservation) 
            MakeReservationPanel.this.getDefaultModelObject();
        
        // change status if we need an authorizor
        if (
            (res.getReserver().getAuthorizor() != null &&
              !res.getReserver().getAuthorizor().equals(res.getReserver()))
            || (res.getResource().getAuthorizor() != null) &&
              !res.getResource().getAuthorizor().equals(res.getReserver())) {
            res.setAuthorization(Status.PENDING);            
        } else {
          res.setAuthorization(Status.APPROVED);
        }
        
        res.setCreation(new Date());
        res.setStart(fromTimeModel.getObject().combineWith(res.getStart()));
        res.setEnd(toTimeModel.getObject().combineWith(res.getEnd()));
        
        res.setBeforeCondition(res.getResource().getCurrentCondition());
        
        reservationDao.saveReservation(res);
        
        mailService.notifyCreated(res);
        
        if (previousPage != null)
          setResponsePage(previousPage);
        else
          setResponsePage(new CalendarPage());
      }      
    };
    
    reservationForm.add(new FeedbackPanel("feedback"));
    
    WebMarkupContainer userChoiceContainer = 
        new WebMarkupContainer("userChoiceContainer");
    
    userChoiceContainer.add(
        new DropDownChoice("userChoice", 
          new PropertyModel(model, "reserver"),
          userDao.getAllUsers()));
    userChoiceContainer
        .setVisible(((Session)getSession()).getCurrentUser().isAdmin());
    
    reservationForm.add(userChoiceContainer);
    
    DropDownChoice resourceChoice = new DropDownChoice("resourceChoice", 
          new PropertyModel(model, "resource"),
          resourceDao.getAllResources());
    resourceChoice.setLabel(new Model("Resource"));
    resourceChoice.add(new AbstractValidator(){

      @Override
      protected void onValidate(IValidatable validatable) {
        Resource r = (Resource) validatable.getValue();
        
        if (r == null)
          error(validatable);
      }

      @Override
      protected String resourceKey() {
        return "MustBeValid";
      }

      @Override
      public boolean validateOnNullValue() {
        return true;
      }      
    });
    
    reservationForm.add(
        new FormComponentFeedbackBorder("resourceChoiceBorder")
          .add(resourceChoice));    
    
    DateField fromDate = new DateField("fromDate", 
        new PropertyModel(model, "start"));
    fromDate.setRequired(true);
    fromDate.setLabel(new Model("From Date"));
    reservationForm.add(fromDate);
        
    fromTimeModel = new Model<QuantizedTime>(
        closestTime(model.getObject().getStart())); 
    DropDownChoice fromTime = new DropDownChoice("fromTime", 
          fromTimeModel,
          Arrays.asList(times));
    reservationForm.add(fromTime);    
    
    DateField toDate = new DateField("toDate", 
        new PropertyModel(model, "end"));
    toDate.setRequired(true);
    toDate.setLabel(new Model("To Date"));
    reservationForm.add(toDate);
        
    toTimeModel = new Model<QuantizedTime>(
        closestTime(model.getObject().getEnd())); 
    DropDownChoice toTime = new DropDownChoice("toTime", 
          toTimeModel,
          Arrays.asList(times));
    reservationForm.add(toTime);
    
    reservationForm.add(
        new NoConflictingReservations(model.getObject(), resourceChoice, 
        fromDate, fromTime, toDate, toTime));
    
    reservationForm.add(
        new Button("makeReservationButton", 
        new PropertyModel(this, "reservationButtonText")));
    
    Button cancelReservationButton = new Button("cancelReservationButton", 
        new Model("Don't change reservation")){
      @Override
      public void onSubmit() {        
        setResponsePage(previousPage);
      }
    };
    cancelReservationButton.setDefaultFormProcessing(false);
    cancelReservationButton.setVisible(!model.getObject().isNew() 
        && previousPage != null);
    reservationForm.add(cancelReservationButton);
    
    add(reservationForm);
    
  }
  
  public String getReservationButtonText() {
    if (((Reservation)getDefaultModelObject()).isNew())
      return "Make Reservation";
    else
      return "Change Reservation";
  }
  
  private void makeTimes() {
    
    // every half hour
    times = new QuantizedTime[48];
    
    Calendar c = Calendar.getInstance();
    c.set(Calendar.HOUR_OF_DAY, 0);
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);    
    
    SimpleDateFormat sdf = new SimpleDateFormat("h:mm aa");
    
    for (int i = 0; i < times.length; i++) {
      c.getTime();
      
      times[i] = new QuantizedTime(
          c.get(Calendar.HOUR_OF_DAY),
          c.get(Calendar.MINUTE),
          sdf.format(c.getTime()));
      c.add(Calendar.MINUTE, 30);
    }
  }
  
  public static class QuantizedTime implements Serializable {
    private int hour;
    private int minute;
    private String display;

    public QuantizedTime(int hour, int minute, String display) {
      this.hour = hour;
      this.minute = minute;
      this.display = display;
    }

    public String getDisplay() {
      return display;
    }

    public void setDisplay(String display) {
      this.display = display;
    }

    public int getHour() {
      return hour;
    }

    public void setHour(int hour) {
      this.hour = hour;
    }

    public int getMinute() {
      return minute;
    }

    public void setMinute(int minute) {
      this.minute = minute;
    }

    public Date combineWith(Date d) {
      Calendar c = Calendar.getInstance();
      c.setTime(d);
      
      Calendar c2 = new GregorianCalendar(
          c.get(Calendar.YEAR), 
          c.get(Calendar.MONTH),
          c.get(Calendar.DATE),
          hour, minute);
      
          
      return c2.getTime();
    }
    
    @Override
    public String toString() {
      return display;
    }   
  }  
  
  public QuantizedTime closestTime(Date d) {
    Calendar c = Calendar.getInstance();
    
    c.setTime(d);
    
    int currentHour = c.get(Calendar.HOUR_OF_DAY);
    int currentMinute = c.get(Calendar.MINUTE);
        
    for (int i = 0; i < times.length; i++) {
      int bestHour = times[i].getHour();
      int bestMinute = times[i].getMinute();      
      if (bestHour > currentHour || (bestHour == currentHour && bestMinute >= currentMinute)) {
        return times[i];
      }
    }
    
    return times[0];
  } 
  
  public class NoConflictingReservations extends AbstractFormValidator {

    private Reservation existingReservation;
    private DateField fromDate;    
    private DropDownChoice<QuantizedTime> fromTime;
    private DateField toDate;
    private DropDownChoice<QuantizedTime> toTime;
    private DropDownChoice<Resource> resourceChoice;
    
    public NoConflictingReservations(
        Reservation existingReservation,
        DropDownChoice<Resource> resourceChoice,
        DateField fromDate, 
        DropDownChoice<QuantizedTime> fromTime, 
        DateField toDate, 
        DropDownChoice<QuantizedTime> toTime) {
      this.existingReservation = existingReservation;
      this.resourceChoice = resourceChoice;
      this.fromDate = fromDate;
      this.fromTime = fromTime;
      this.toDate = toDate;
      this.toTime = toTime;
    }

    public FormComponent[] getDependentFormComponents() {
      return new FormComponent[]{resourceChoice, fromDate, 
        fromTime, toDate, toTime};
    }

    public void validate(Form<?> form) {           
      Date start = fromTime.getConvertedInput()
          .combineWith(fromDate.getConvertedInput());      
      
      Date end = toTime.getConvertedInput()
          .combineWith(toDate.getConvertedInput());
      if (end.before(new Date())) {
        error(fromDate, "StartBeforeNow");
      } else if (!start.before(end)) {
        error(toDate, "StartBeforeEnd");         
      } else {
        List<Reservation> conflicting =               
          reservationDao.findPossibleConflicts(
            existingReservation,
            resourceChoice.getConvertedInput(),
            start,
            end);      
        
        if (conflicting != null && !conflicting.isEmpty()) {
          Reservation r1 = conflicting.get(0);
          
          Map<String, Object> map = new HashMap<String, Object>();
          map.put("user", r1.getReserver().getName());
          map.put("resource", r1.getResource().getName());
          map.put("start", r1.getStart());
          map.put("end", r1.getEnd());          
          
          error(toDate, "ConflictingReservation", map);
        }
        
        //error(toDate, "Bored");
      }
    }
  }
}